﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SpiceNet.Circuits;
using SpiceNet.Models;
using SpiceNet.Diagnostics;
using SpiceNet.Integration;
using MathNet.Numerics.LinearAlgebra;

namespace SpiceNet.Simulations
{
    /// <summary>
    /// A class that will represent a DC analysis
    /// </summary>
    public class DC : Analysis
    {
        /// <summary>
        /// Constructor
        /// </summary>
        public DC() : base(typeof(TrcvJob))
        {
            Name = "DC";
            Description = "D.C. Transfer curve analysis";
            Domain = DomainTypes.SweepDomain;
            DoIc = true;
        }

        /// <summary>
        /// Wrapper for DCtrCurv in dctrcurv.c
        /// Perform a DC transfer curve simulation
        /// </summary>
        /// <param name="ckt"></param>
        /// <param name="reset"></param>
        public override void Func(Circuit ckt, bool reset)
        {
            // Get the current job
            TrcvJob cv = (TrcvJob)ckt.CurrentJob;

            // Find the type codes for voltage sources and current sources
            Model[] vcode = ckt.FindModels(typeof(SpiceNet.Models.VoltageSourceModel));
            Model[] icode = ckt.FindModels(typeof(SpiceNet.Models.CurrentSourceModel));

            Vector<double> temp;
            // bool converged;
            int i, j;
            // int error;
            // long save;
            // int numNames;
            bool firstTime = true;

            if (!reset && cv.TRCVnestState >= 0)
            {
                /* continuing */
                i = cv.TRCVnestState;
                goto resume;
            }
            ckt.State.Time = 0;
            ckt.Integration.Delta = cv.Sweeps[0].TRCVvStep;
            ckt.Mode = (ckt.Mode & Circuit.Modes.Uic) | Circuit.Modes.DcTranCurve | Circuit.Modes.InitJct;
            ckt.Integration.Order = 1;

            for (i = 0; i < ckt.Integration.DeltaOld.Length; i++)
            {
                ckt.Integration.DeltaOld[i] = ckt.Integration.Delta;
            }
            for (i = 0; i <= cv.TRCVnestLevel; i++)
            {
                /* Loop through voltage sources if any */
                for (j = 0; i < (vcode?.Length ?? 0); i++)
                {
                    VoltageSource vhere = vcode[i].Find(cv.Sweeps[i].TRCVvName) as VoltageSource;
                    if (vhere != null)
                    {
                        cv.Sweeps[i].TRCVvElt = vhere;
                        cv.Sweeps[i].TRCVvSave = vhere.VSRCdcValue.Clone();
                        // cv.Sweeps[i].TRCVvType = vhere.GetType();
                        vhere.VSRCdcValue.Value = cv.Sweeps[i].TRCVvStart;
                        goto found;
                    }
                }

                /* Loop through current sources if any */
                for (j = 0; i < (icode?.Length ?? 0); i++)
                {
                    CurrentSource ihere = icode[i].Find(cv.Sweeps[i].TRCVvName) as CurrentSource;
                    if (ihere != null)
                    {
                        cv.Sweeps[i].TRCVvElt = ihere;
                        cv.Sweeps[i].TRCVvSave = ihere.ISRCdcValue.Clone();
                        // cv.Sweeps[i].TRCVvType = ihere.GetType();
                        ihere.ISRCdcValue.Value = cv.Sweeps[i].TRCVvStart;
                        goto found;
                    }
                }
                throw new CircuitException(String.Format("DCtrCurv: source {0} not in circuit", cv.Sweeps[i].TRCVvName));

            found:
                ;
            }
            /* error = CKTnames(ckt,&numNames,&nameList);
            if (error) return(error);
            (*(SPfrontEnd.IFnewUid))((GENERIC *)ckt,&varUid,(IFuid )NULL,
                                      "sweep", UID_OTHER, (GENERIC **)NULL);
            error = (*(SPfrontEnd.OUTpBeginPlot))((GENERIC *)ckt,
                                                   (GENERIC*)ckt.CKTcurJob, ckt.CKTcurJob.JOBname,
                                                   varUid,IF_REAL,numNames,nameList, IF_REAL,&plot);
            if (error) return(error); */
            /* now have finished the initialization - can start doing hard part */
            i = 0;
        resume:
            for (; ; )
            {

                if (cv.Sweeps[i].TRCVvElt is VoltageSource)
                {
                    if (((cv.Sweeps[i].TRCVvElt as VoltageSource).VSRCdcValue) *
                        Math.Sign(cv.Sweeps[i].TRCVvStep) >
                        Math.Sign(cv.Sweeps[i].TRCVvStep) *
                        cv.Sweeps[i].TRCVvStop)
                    {
                        i++;
                        firstTime = true;
                        ckt.Mode = (ckt.Mode & Circuit.Modes.Uic) |
                                       Circuit.Modes.DcTranCurve | Circuit.Modes.InitJct;
                        if (i > cv.TRCVnestLevel) break;
                        goto nextstep;
                    }
                }
                else if (cv.Sweeps[i].TRCVvElt is CurrentSource)
                { /* current source */
                    if (((cv.Sweeps[i].TRCVvElt as CurrentSource).ISRCdcValue) *
                        Math.Sign(cv.Sweeps[i].TRCVvStep) >
                        Math.Sign(cv.Sweeps[i].TRCVvStep) *
                        cv.Sweeps[i].TRCVvStop)
                    {
                        i++;
                        firstTime = true;
                        ckt.Mode = (ckt.Mode & Circuit.Modes.Uic) |
                                       Circuit.Modes.DcTranCurve | Circuit.Modes.InitJct;
                        if (i > cv.TRCVnestLevel) break;
                        goto nextstep;
                    }
                } /* else  not possible */
                while (i > 0)
                {
                    /* init(i); */
                    i--;
                    if (cv.Sweeps[i].TRCVvElt is VoltageSource)
                    { /* voltage source */
                        (cv.Sweeps[i].TRCVvElt as VoltageSource).VSRCdcValue.Value =
                            cv.Sweeps[i].TRCVvStart;
                    }
                    else if (cv.Sweeps[i].TRCVvElt is CurrentSource)
                    { /* current source */
                        (cv.Sweeps[i].TRCVvElt as CurrentSource).ISRCdcValue.Value =
                            cv.Sweeps[i].TRCVvStart;
                    } /* else not possible */
                }

                temp = ckt.State.States[ckt.Integration.MaxOrder + 1];
                for (j = ckt.Integration.MaxOrder; j >= 0; j--)
                {
                    ckt.State.States[j + 1] = ckt.State.States[j];
                }
                ckt.State.States[0] = temp;

                /* do operation */
                if (!Iterate(ckt, ckt.Config.DcTrcvMaxIterations))
                {
                    Op(
                        ckt,
                        (ckt.Mode & Circuit.Modes.Uic) | Circuit.Modes.DcTranCurve | Circuit.Modes.InitJct,
                        (ckt.Mode & Circuit.Modes.Uic) | Circuit.Modes.DcTranCurve | Circuit.Modes.InitFloat,
                        ckt.Config.DcTrcvMaxIterations);
                }
                ckt.Mode = (ckt.Mode & Circuit.Modes.Uic) | Circuit.Modes.DcTranCurve | Circuit.Modes.InitPred;
                if (cv.Sweeps[0].TRCVvElt is VoltageSource)
                {
                    ckt.State.Time = (cv.Sweeps[i].TRCVvElt as VoltageSource).VSRCdcValue;
                }
                else if (cv.Sweeps[0].TRCVvElt is CurrentSource)
                {
                    ckt.State.Time = (cv.Sweeps[i].TRCVvElt as CurrentSource).ISRCdcValue;
                }

                Dump(ckt);
                if (firstTime)
                {
                    firstTime = false;
                    ckt.State.States[0].CopyTo(ckt.State.States[1]);
                }

            nextstep:
                ;

                /* if (cv.Sweeps[i].TRCVvElt is VoltageSource)
                { // voltage source
                    (cv.Sweeps[i].TRCVvElt as VoltageSource).VSRCdcValue.Value += cv.Sweeps[i].TRCVvStep;
                }
                else if (cv.Sweeps[i].TRCVvElt is CurrentSource)
                { // current source
                    (cv.Sweeps[i].TRCVvElt as CurrentSource).ISRCdcValue.Value += cv.Sweeps[i].TRCVvStep;
                } // else not possible
                /* if ( (*(SPfrontEnd.IFpauseTest))() ) {
                    // user asked us to pause, so save state
                    cv.TRCVnestState = i;
                    return(E_PAUSE);
                } */

                // We can do this for any source with a DC value
                // NOTE: We are first calculating the current step because += will cumulate errors
                double cvalue = (double)cv.Sweeps[i].TRCVvElt["dc"];
                int cstep = (int)Math.Round((cvalue - cv.Sweeps[i].TRCVvStart) / cv.Sweeps[i].TRCVvStep, MidpointRounding.AwayFromZero);
                cstep++; // Next step
                cv.Sweeps[i].TRCVvElt["dc"] = cv.Sweeps[i].TRCVvStart + cstep * cv.Sweeps[i].TRCVvStep;
            }

            /* all done, lets put everything back */
            for (i = 0; i <= cv.TRCVnestLevel; i++)
            {
                if (cv.Sweeps[i].TRCVvElt is VoltageSource)
                {  /* voltage source */
                    (cv.Sweeps[i].TRCVvElt as VoltageSource).VSRCdcValue.Set(cv.Sweeps[i].TRCVvSave);
                }
                else /* if(cv.Sweeps[i].TRCVvType == icode) current source */
                {
                    (cv.Sweeps[i].TRCVvElt as CurrentSource).ISRCdcValue.Set(cv.Sweeps[i].TRCVvSave);
                } /* else not possible */
            }
            return;
        }
    }
}
